using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


namespace Sublimation
{
    public abstract class GameObject : Microsoft.Xna.Framework.DrawableGameComponent
    {
        #region Attributes
        // AI
        protected List<Behaviours.Behaviour> behaviours;
        protected float aiSightRange;
        public float AISightRange
        {
            get { return this.aiSightRange; }
            set { aiSightRange = value; }
        }

        protected float collisionRadius;
        public virtual float CollisionRadius
        {
            get { return this.collisionRadius; }
            set { this.collisionRadius = value; }
        }
        protected WorldProperties worldProperties;
        protected Vector2 position;
        public Vector2 Position
        {
            get { return this.position; }
            set
            {
                this.position = value;
                UpdateBoundingRectangle();
            }
        }   

        protected Vector2 origin;
        public Vector2 Origin
        {
            get { return this.origin; }
            set
            {
                // update the position for the new origin
                this.Position += value - this.Origin;
                //set the origin
                this.origin = value;
            }
        }

        protected float scale;
        public float Scale
        {
            get { return this.scale; }
            set
            {
                this.scale = value;
                UpdateBoundingRectangle();
            }
        }

        protected float rotation;
        public float Rotation
        {
            get { return this.rotation; }
            set { this.rotation = value; }
        }
        protected Vector2 velocity;
        public Vector2 Velocity
        {
            get { return this.velocity; }
            set {
                if (value.Length() > MaxSpeed) this.velocity = Vector2.Normalize(value) * MaxSpeed;
                else this.velocity = value;
            }
        }
        protected float maxSpeed;
        public float MaxSpeed
        {
            get { return maxSpeed; }
            set { this.maxSpeed = value; }
        }
        protected float maxTurnRadiansPerSec;
        public float MaxTurnRadiansPerSec
        {
            get { return maxTurnRadiansPerSec; }
            set { this.maxTurnRadiansPerSec = value; }
        }
        protected Texture2D sprite;
        public Texture2D Sprite
        {
            get { return this.sprite; }
            set
            {
                this.sprite = value;
                UpdateBoundingRectangle();
            }
        }

        protected Color spriteColor = Color.White;
        public Color SpriteColor
        {
            get { return this.spriteColor; }
            set { this.spriteColor = value;}
        }
        protected Rectangle boundingRectangle;
        public Rectangle BoundingRectangle
        {
            get { return this.boundingRectangle; }
        }
        #endregion

        //XXX: this is a major slowdown, its all a bit messy now :S
        public virtual void UpdateBoundingRectangle()
        {
            // position is at the origin, so need to take that into account otherwise the bounding rectangle will be in a strange position
            if (Sprite != null) boundingRectangle = new Rectangle((int)(Position.X), (int)(Position.Y), (int)(Math.Round(Sprite.Width * Scale, 0, MidpointRounding.AwayFromZero)), (int)(Math.Round(Sprite.Height * Scale, 0, MidpointRounding.AwayFromZero)));
            else boundingRectangle = new Rectangle((int)(Position.X), (int)(Position.Y), boundingRectangle.Width, boundingRectangle.Height);
        }

        public GameObject(WorldProperties worldProperties)
            : base(worldProperties.Game)
        {
            this.worldProperties = worldProperties;
            worldProperties.Game.Components.Add(this);

            SetupBehaviours();
        }

        protected override void LoadContent()
        {
            base.LoadContent();

            // Setup a SpriteBatch for this set of rendering
            spriteBatch = worldProperties.SpriteBatch;
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            //double timer = System.Runtime.
            
            UpdateBehaviour(gameTime);

            DoMovement(gameTime);

            CheckCollisions(gameTime);
        }

        public virtual void DoMovement(GameTime gameTime)
        {
            Position += Velocity * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }

        protected SpriteBatch spriteBatch;
        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            if (Sprite != null)
            {
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(Sprite, Position, null, SpriteColor, Rotation, Origin, Scale, SpriteEffects.None, 1);
                spriteBatch.End();
            }
        }

        public virtual void Kill()
        {
            // this should destroy the object and possibly spawn a special graphical effect (eg explosion)
            Enabled = false;
            Visible = false;
            // prepare this for garbage collection
            Game.Components.Remove(this);
        }

        public virtual void ApplyDamage(float damage)
        {
            // This should apply any damage appropriate to the game object, whether that involves life, energy or whatever
            // Do nothing for a basic game object
        }

        // this scales the obeject to a size relative to the parent with 1.0 being the size of the larger dimension - does not reposition
        public void ScaleOverObject(GameObject other, float scale)
        {
            float amountToScale = Math.Max(other.BoundingRectangle.Width, other.BoundingRectangle.Height) * scale / Math.Min(this.BoundingRectangle.Width, this.BoundingRectangle.Height);
            this.boundingRectangle.Width = (int)(this.boundingRectangle.Width * amountToScale);
            this.boundingRectangle.Height = (int)(this.boundingRectangle.Height * amountToScale);
            this.scale = this.scale * amountToScale;
        }

        // scales the object to fit within the other object
        public void ScaleWithinObject(GameObject other, float scale)
        {
            float amountToScale = Math.Min(other.BoundingRectangle.Width, other.BoundingRectangle.Height) * scale / Math.Max(this.BoundingRectangle.Width, this.BoundingRectangle.Height);
            this.boundingRectangle.Width = (int)(this.boundingRectangle.Width * amountToScale);
            this.boundingRectangle.Height = (int)(this.boundingRectangle.Height * amountToScale);
            this.scale = this.scale * amountToScale;
        }

        // This positions the centre of this object to the centre of another
        public void CentreOnObject(GameObject other)
        {
            Vector2 thisCentre = new Vector2(this.BoundingRectangle.Width / 2, this.BoundingRectangle.Height / 2);
            Vector2 otherCentre = new Vector2(other.BoundingRectangle.Width / 2, other.BoundingRectangle.Height / 2);
            // other.Position - other.Origin + Origin aligns the top left corners : THE ORIGIN MUST SCALE
            this.Position = other.Position - other.Origin * other.Scale + Origin * Scale - thisCentre + otherCentre;
        }

        // positions the origin of this object to the origin of the other regardless of size difference so they rotate together
        public void AlignOriginWithObject(GameObject other)
        {
            // convert to screen coords and then back, so other coords, screen coords, this coords
            // screen coords are the position of the other - so so convert to this coords
            this.origin = this.origin - (this.Position - other.Position);
        }

        public virtual void SetOriginToCentre()
        {
            this.Origin = GetCenter();
        }

        public virtual Vector2 GetCenter()
        {
            return new Vector2(this.BoundingRectangle.Width / 2, this.BoundingRectangle.Height / 2);
        }

        /*
        public Vector2 GetCollisionCenter()
        {
            return new Vector2(Position.X + (float)((boundingRectangle.X/2) * Math.Cos(Rotation) * Scale), Position.Y + (float)((boundingRectangle.Y/2) * Math.Sin(Rotation) * Scale));
        }*/

        public virtual void CheckCollisions(GameTime gameTime)
        {


            // override and check any other objects to collide with
        }

        public bool CollidesWith(GameObject target)
        {
            if (Vector2.Distance(Position, target.Position) <= CollisionRadius + target.CollisionRadius)
            {
                return true;
            }
            return false;
        }

        protected void UpdateBehaviour(GameTime gameTime)
        {
            if (behaviours.Count > 0)
            {
                Vector2 newVelocity = Vector2.Zero;

                float totalWeight = 0.0f;
                foreach (Behaviours.Behaviour behaviour in behaviours)
                {
                    newVelocity += behaviour.RunBehaviour();
                    totalWeight += behaviour.getWeight();
                }
                //newVelocity = (behaviours.Count > 1 ? newVelocity / behaviours.Count : newVelocity);
                //XXX: actually maybe dont do this and just let it get clamped if it needs to be
                //newVelocity = (totalWeight > 0 ? newVelocity / totalWeight : newVelocity);

                // clamp turn rate
                float oldAngle = (float)Math.Atan2(Velocity.Y, Velocity.X);
                float desiredAngle = (float)Math.Atan2(newVelocity.Y, newVelocity.X);
                float newAngle;
                if (maxTurnRadiansPerSec != -1)
                {
                    newAngle = MathHelper.Clamp(WrapAngle(desiredAngle), WrapAngle(oldAngle - MaxTurnRadiansPerSec * (float)gameTime.ElapsedGameTime.TotalSeconds),
                            WrapAngle(oldAngle + MaxTurnRadiansPerSec * (float)gameTime.ElapsedGameTime.TotalSeconds));
                }
                else
                {
                    newAngle = desiredAngle;
                }
                Velocity = Vector2.Normalize(new Vector2((float)Math.Cos(newAngle), (float)Math.Sin(newAngle))) * newVelocity.Length();
                //Velocity = newVelocity;
            }
        }

        protected virtual void SetupBehaviours()
        {
            this.behaviours = new List<Behaviours.Behaviour>();
        }

        private static float WrapAngle(float radians)
        {
            while (radians < -MathHelper.Pi)
            {
                radians += MathHelper.TwoPi;
            }
            while (radians > MathHelper.Pi)
            {
                radians -= MathHelper.TwoPi;
            }
            return radians;
        }

        // Rotates a vector around a arbitrary point
        public Vector2 RotatePoint(Vector2 origin, float radians, Vector2 sourceVector)
        {
            Matrix myRotationMatrix = Matrix.CreateRotationZ(radians);

            // Rotate relative to origin.
            Vector2 rotatedVector = Vector2.Transform(sourceVector - origin, myRotationMatrix);

            // Add origin to get final location.
            sourceVector = rotatedVector + origin;
            return sourceVector;
        }

        // checks if game object contains a point
        public bool ContainsPoint(Vector2 point)
        {
            return (point.X >= this.position.X && point.X <= this.position.X + this.sprite.Width
                 && point.Y >= this.position.Y && point.Y <= this.position.Y + this.sprite.Height);
        }
    }
}